1 package org.apache.maven.surefire.junitcore.pc;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.surefire.junitcore.JUnitCoreParameters;
23 import org.apache.maven.surefire.testset.TestSetFailedException;
24 import org.junit.internal.runners.ErrorReportingRunner;
25 import org.junit.runner.Description;
26 import org.junit.runner.Runner;
27 import org.junit.runner.manipulation.Filter;
28 import org.junit.runner.manipulation.NoTestsRemainException;
29 import org.junit.runner.notification.RunNotifier;
30 import org.junit.runners.ParentRunner;
31 import org.junit.runners.Suite;
32 import org.junit.runners.model.InitializationError;
33 import org.junit.runners.model.RunnerBuilder;
34
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.EnumMap;
39 import java.util.Iterator;
40 import java.util.LinkedHashSet;
41 import java.util.Map;
42 import java.util.concurrent.ExecutorService;
43 import java.util.concurrent.Executors;
44
45 import static org.apache.maven.surefire.junitcore.pc.ParallelComputerUtil.*;
46 import static org.apache.maven.surefire.junitcore.pc.Type.*;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 public final class ParallelComputerBuilder
73 {
74 static final int TOTAL_POOL_SIZE_UNDEFINED = 0;
75
76 private final Map<Type, Integer> parallelGroups = new EnumMap<Type, Integer>( Type.class );
77
78 private boolean useSeparatePools;
79
80 private int totalPoolSize;
81
82 private JUnitCoreParameters parameters;
83
84 private boolean optimize;
85
86 private boolean runningInTests;
87
88
89
90
91
92
93 ParallelComputerBuilder()
94 {
95 runningInTests = true;
96 useSeparatePools();
97 parallelGroups.put( SUITES, 0 );
98 parallelGroups.put( CLASSES, 0 );
99 parallelGroups.put( METHODS, 0 );
100 }
101
102 public ParallelComputerBuilder( JUnitCoreParameters parameters )
103 {
104 this();
105 runningInTests = false;
106 this.parameters = parameters;
107 }
108
109 public ParallelComputer buildComputer()
110 {
111 return new PC();
112 }
113
114 ParallelComputerBuilder useSeparatePools()
115 {
116 totalPoolSize = TOTAL_POOL_SIZE_UNDEFINED;
117 useSeparatePools = true;
118 return this;
119 }
120
121 ParallelComputerBuilder useOnePool()
122 {
123 totalPoolSize = TOTAL_POOL_SIZE_UNDEFINED;
124 useSeparatePools = false;
125 return this;
126 }
127
128
129
130
131
132
133
134 ParallelComputerBuilder useOnePool( int totalPoolSize )
135 {
136 if ( totalPoolSize < 1 )
137 {
138 throw new IllegalArgumentException( "Size of common pool is less than 1." );
139 }
140 this.totalPoolSize = totalPoolSize;
141 useSeparatePools = false;
142 return this;
143 }
144
145 boolean isOptimized()
146 {
147 return optimize;
148 }
149
150 ParallelComputerBuilder optimize( boolean optimize )
151 {
152 this.optimize = optimize;
153 return this;
154 }
155
156 ParallelComputerBuilder parallelSuites()
157 {
158 return parallel( SUITES );
159 }
160
161 ParallelComputerBuilder parallelSuites( int nThreads )
162 {
163 return parallel( nThreads, SUITES );
164 }
165
166 ParallelComputerBuilder parallelClasses()
167 {
168 return parallel( CLASSES );
169 }
170
171 ParallelComputerBuilder parallelClasses( int nThreads )
172 {
173 return parallel( nThreads, CLASSES );
174 }
175
176 ParallelComputerBuilder parallelMethods()
177 {
178 return parallel( METHODS );
179 }
180
181 ParallelComputerBuilder parallelMethods( int nThreads )
182 {
183 return parallel( nThreads, METHODS );
184 }
185
186 private ParallelComputerBuilder parallel( int nThreads, Type parallelType )
187 {
188 if ( nThreads < 0 )
189 {
190 throw new IllegalArgumentException( "negative nThreads " + nThreads );
191 }
192
193 if ( parallelType == null )
194 {
195 throw new NullPointerException( "null parallelType" );
196 }
197
198 parallelGroups.put( parallelType, nThreads );
199 return this;
200 }
201
202 private ParallelComputerBuilder parallel( Type parallelType )
203 {
204 return parallel( Integer.MAX_VALUE, parallelType );
205 }
206
207 private double parallelTestsTimeoutInSeconds()
208 {
209 return parameters == null ? 0d : parameters.getParallelTestsTimeoutInSeconds();
210 }
211
212 private double parallelTestsTimeoutForcedInSeconds()
213 {
214 return parameters == null ? 0d : parameters.getParallelTestsTimeoutForcedInSeconds();
215 }
216
217 final class PC
218 extends ParallelComputer
219 {
220 final Collection<ParentRunner> suites = new LinkedHashSet<ParentRunner>();
221
222 final Collection<ParentRunner> nestedSuites = new LinkedHashSet<ParentRunner>();
223
224 final Collection<ParentRunner> classes = new LinkedHashSet<ParentRunner>();
225
226 final Collection<ParentRunner> nestedClasses = new LinkedHashSet<ParentRunner>();
227
228 final Collection<Runner> unscheduledRunners = new LinkedHashSet<Runner>();
229
230 int poolCapacity;
231
232 boolean splitPool;
233
234 private final Map<Type, Integer> allGroups;
235
236 private long nestedClassesChildren;
237
238 private volatile Scheduler master;
239
240 private PC()
241 {
242 super( parallelTestsTimeoutInSeconds(), parallelTestsTimeoutForcedInSeconds() );
243 allGroups = new EnumMap<Type, Integer>( ParallelComputerBuilder.this.parallelGroups );
244 poolCapacity = ParallelComputerBuilder.this.totalPoolSize;
245 splitPool = ParallelComputerBuilder.this.useSeparatePools;
246 }
247
248 @Override
249 public Collection<Description> shutdown( boolean shutdownNow )
250 {
251 final Scheduler master = this.master;
252 return master == null ? Collections.<Description>emptyList() : master.shutdown( shutdownNow );
253 }
254
255 @Override
256 public Runner getSuite( RunnerBuilder builder, Class<?>[] cls )
257 throws InitializationError
258 {
259 try
260 {
261 super.getSuite( builder, cls );
262 populateChildrenFromSuites();
263
264 WrappedRunners suiteSuites = wrapRunners( suites );
265 WrappedRunners suiteClasses = wrapRunners( classes );
266
267 long suitesCount = suites.size();
268 long classesCount = classes.size() + nestedClasses.size();
269 long methodsCount = suiteClasses.embeddedChildrenCount + nestedClassesChildren;
270 if (!ParallelComputerBuilder.this.runningInTests)
271 {
272 determineThreadCounts( suitesCount, classesCount, methodsCount );
273 }
274
275 return setSchedulers( suiteSuites.wrappingSuite, suiteClasses.wrappingSuite );
276 }
277 catch ( TestSetFailedException e )
278 {
279 throw new InitializationError( e );
280 }
281 }
282
283 @Override
284 protected Runner getRunner( RunnerBuilder builder, Class<?> testClass )
285 throws Throwable
286 {
287 Runner runner = super.getRunner( builder, testClass );
288 if ( canSchedule( runner ) )
289 {
290 if ( runner instanceof Suite )
291 {
292 suites.add( (Suite) runner );
293 }
294 else
295 {
296 classes.add( (ParentRunner) runner );
297 }
298 }
299 else
300 {
301 unscheduledRunners.add( runner );
302 }
303 return runner;
304 }
305
306 private void determineThreadCounts( long suites, long classes, long methods )
307 throws TestSetFailedException
308 {
309 final JUnitCoreParameters parameters = ParallelComputerBuilder.this.parameters;
310 final boolean optimize = ParallelComputerBuilder.this.optimize;
311 RunnerCounter counts = new RunnerCounter( suites, classes, methods );
312 Concurrency concurrency = resolveConcurrency( parameters, optimize ? counts : null );
313 allGroups.put( SUITES, concurrency.suites );
314 allGroups.put( CLASSES, concurrency.classes );
315 allGroups.put( METHODS, concurrency.methods );
316 poolCapacity = concurrency.capacity;
317 splitPool &= concurrency.capacity <= 0;
318 }
319
320 private <T extends Runner> WrappedRunners wrapRunners( Collection<T> runners )
321 throws InitializationError
322 {
323
324 long childrenCounter = 0;
325 ArrayList<Runner> runs = new ArrayList<Runner>();
326 for ( T runner : runners )
327 {
328 if ( runner != null )
329 {
330 int children = countChildren( runner );
331 childrenCounter += children;
332 if ( children != 0 )
333 {
334 runs.add( runner );
335 }
336 }
337 }
338
339 Suite wrapper = runs.isEmpty() ? null : new Suite( null, runs )
340 {
341 };
342 return new WrappedRunners( wrapper, childrenCounter );
343 }
344
345 private int countChildren( Runner runner )
346 {
347 Description description = runner.getDescription();
348 Collection children = description == null ? null : description.getChildren();
349 return children == null ? 0 : children.size();
350 }
351
352 private ExecutorService createPool( int poolSize )
353 {
354 return poolSize < Integer.MAX_VALUE
355 ? Executors.newFixedThreadPool( poolSize )
356 : Executors.newCachedThreadPool();
357 }
358
359 private Scheduler createMaster( ExecutorService pool, int poolSize )
360 {
361 if ( !areSuitesAndClassesParallel() || poolSize <= 1 )
362 {
363 return new Scheduler( null, new InvokerStrategy() );
364 }
365 else if ( pool != null && poolSize == Integer.MAX_VALUE )
366 {
367 return new Scheduler( null, new SharedThreadPoolStrategy( pool ) );
368 }
369 else
370 {
371 return new Scheduler( null, SchedulingStrategies.createParallelStrategy( 2 ) );
372 }
373 }
374
375 private boolean areSuitesAndClassesParallel()
376 {
377 return !suites.isEmpty() && allGroups.get( SUITES ) > 0 && !classes.isEmpty()
378 && allGroups.get( CLASSES ) > 0;
379 }
380
381 private void populateChildrenFromSuites()
382 {
383
384 Filter filter = new SuiteFilter();
385 for ( Iterator<ParentRunner> it = suites.iterator(); it.hasNext(); )
386 {
387 ParentRunner suite = it.next();
388 try
389 {
390 suite.filter( filter );
391 }
392 catch ( NoTestsRemainException e )
393 {
394 it.remove();
395 }
396 }
397 }
398
399 private int totalPoolSize()
400 {
401 if ( poolCapacity == TOTAL_POOL_SIZE_UNDEFINED )
402 {
403 int total = 0;
404 for ( int nThreads : allGroups.values() )
405 {
406 total += nThreads;
407 if ( total < 0 )
408 {
409 total = Integer.MAX_VALUE;
410 break;
411 }
412 }
413 return total;
414 }
415 else
416 {
417 return poolCapacity;
418 }
419 }
420
421 private Runner setSchedulers( ParentRunner suiteSuites, ParentRunner suiteClasses )
422 throws InitializationError
423 {
424 int parallelSuites = allGroups.get( SUITES );
425 int parallelClasses = allGroups.get( CLASSES );
426 int parallelMethods = allGroups.get( METHODS );
427 int poolSize = totalPoolSize();
428 ExecutorService commonPool = splitPool || poolSize == 0 ? null : createPool( poolSize );
429 master = createMaster( commonPool, poolSize );
430
431 if ( suiteSuites != null )
432 {
433
434 if ( commonPool != null && parallelSuites > 0 )
435 {
436 Balancer balancer = BalancerFactory.createBalancerWithFairness( parallelSuites );
437 suiteSuites.setScheduler( createScheduler( null, commonPool, true, balancer ) );
438 }
439 else
440 {
441 suiteSuites.setScheduler( createScheduler( parallelSuites ) );
442 }
443 }
444
445
446 ArrayList<ParentRunner> allSuites = new ArrayList<ParentRunner>( suites );
447 allSuites.addAll( nestedSuites );
448 if ( suiteClasses != null )
449 {
450 allSuites.add( suiteClasses );
451 }
452 if ( !allSuites.isEmpty() )
453 {
454 setSchedulers( allSuites, parallelClasses, commonPool );
455 }
456
457
458 ArrayList<ParentRunner> allClasses = new ArrayList<ParentRunner>( classes );
459 allClasses.addAll( nestedClasses );
460 if ( !allClasses.isEmpty() )
461 {
462 setSchedulers( allClasses, parallelMethods, commonPool );
463 }
464
465
466 ParentRunner all = createFinalRunner( suiteSuites, suiteClasses );
467 all.setScheduler( master );
468 return all;
469 }
470
471 private ParentRunner createFinalRunner( Runner... runners )
472 throws InitializationError
473 {
474 ArrayList<Runner> all = new ArrayList<Runner>( unscheduledRunners );
475 for ( Runner runner : runners )
476 {
477 if ( runner != null )
478 {
479 all.add( runner );
480 }
481 }
482
483 return new Suite( null, all )
484 {
485 @Override
486 public void run( RunNotifier notifier )
487 {
488 try
489 {
490 beforeRunQuietly();
491 super.run( notifier );
492 }
493 finally
494 {
495 afterRunQuietly();
496 }
497 }
498 };
499 }
500
501 private void setSchedulers( Iterable<? extends ParentRunner> runners, int poolSize, ExecutorService commonPool )
502 {
503 if ( commonPool != null )
504 {
505 Balancer concurrencyLimit = BalancerFactory.createBalancerWithFairness( poolSize );
506 boolean doParallel = poolSize > 0;
507 for ( ParentRunner runner : runners )
508 {
509 runner.setScheduler(
510 createScheduler( runner.getDescription(), commonPool, doParallel, concurrencyLimit ) );
511 }
512 }
513 else
514 {
515 ExecutorService pool = null;
516 if ( poolSize == Integer.MAX_VALUE )
517 {
518 pool = Executors.newCachedThreadPool();
519 }
520 else if ( poolSize > 0 )
521 {
522 pool = Executors.newFixedThreadPool( poolSize );
523 }
524 boolean doParallel = pool != null;
525 for ( ParentRunner runner : runners )
526 {
527 runner.setScheduler( createScheduler( runner.getDescription(), pool, doParallel,
528 BalancerFactory.createInfinitePermitsBalancer() ) );
529 }
530 }
531 }
532
533 private Scheduler createScheduler( Description desc, ExecutorService pool, boolean doParallel,
534 Balancer concurrency )
535 {
536 doParallel &= pool != null;
537 SchedulingStrategy strategy = doParallel ? new SharedThreadPoolStrategy( pool ) : new InvokerStrategy();
538 return new Scheduler( desc, master, strategy, concurrency );
539 }
540
541 private Scheduler createScheduler( int poolSize )
542 {
543 if ( poolSize == Integer.MAX_VALUE )
544 {
545 return new Scheduler( null, master, SchedulingStrategies.createParallelStrategyUnbounded() );
546 }
547 else if ( poolSize == 0 )
548 {
549 return new Scheduler( null, master, new InvokerStrategy() );
550 }
551 else
552 {
553 return new Scheduler( null, master, SchedulingStrategies.createParallelStrategy( poolSize ) );
554 }
555 }
556
557 private boolean canSchedule( Runner runner )
558 {
559 return !( runner instanceof ErrorReportingRunner ) && runner instanceof ParentRunner;
560 }
561
562 private class SuiteFilter
563 extends Filter
564 {
565
566
567 @Override
568 public boolean shouldRun( Description description )
569 {
570 return true;
571 }
572
573 @Override
574 public void apply( Object child )
575 throws NoTestsRemainException
576 {
577 super.apply( child );
578 if ( child instanceof Suite )
579 {
580 nestedSuites.add( (Suite) child );
581 }
582 else if ( child instanceof ParentRunner )
583 {
584 ParentRunner parentRunner = (ParentRunner) child;
585 nestedClasses.add( parentRunner );
586 nestedClassesChildren += parentRunner.getDescription().getChildren().size();
587 }
588 }
589
590 @Override
591 public String describe()
592 {
593 return "";
594 }
595 }
596 }
597 }